package logic

import (
	"article_vote/config"
	"article_vote/models"
	"errors"
	"fmt"
	"github.com/go-redis/redis"
	"strconv"
	"time"
)

type ArticleLogic struct {}

func (model *ArticleLogic) GetArticleList(ids []int) []models.Author {
	dbObj, _ := config.GetDbObj("default")
	var authors []models.Author
	err := dbObj.Preload("Atricles").Where("id in (?)", ids).Find(&authors).Error
	if err != nil {
		return nil
	}
	return authors
}

func (model *ArticleLogic) Add(title string, link string, authorId string) error {
	//获取MySQL和redis实例
	dbObj, _ := config.GetDbObj("default")

	//写入文章表
	aId, _ := strconv.Atoi(authorId)
	article := &models.Atricle{}
	article.Title = title
	article.Link = link
	article.AuthorId = aId
	article.Vote = 0
	err := dbObj.Create(article).Error
	if err != nil {
		return err
	}

	//使用关联模型查询作者信息
	dbObj.Model(article).Related(&article.Author)

	//redis 文章信息
	err = addArticleToRedis(article)
	if err != nil {
		return err
	}

	//redis 时间、投票有序集合
	err = addRedisZset(article)
	if err != nil {
		return err
	}

	//redis 记录投票的用户集合，期限为一周
	err = addRedisSet(article.ID, article.AuthorId)
	return nil
}

func (model *ArticleLogic) Vote(articleId string, userId string) error {
	dbObj, _ := config.GetDbObj("default")
	redisObj, _ := config.GetRedis("default")

	weekTime := (time.Hour * 24 * 7).Seconds()
	var voteNum float64 = 432
	cutoff := time.Now().Unix() - int64(weekTime)
	articleKey := "article:" + articleId
	createTime, _ := strconv.ParseInt(redisObj.HGet(articleKey, "time").Val(), 10, 64)

	if cutoff > createTime {
		return errors.New("只能对一周内发布的文章进行投票")
	}

	voted := "voted:" + articleId
	if res, _ := redisObj.SAdd(voted, userId).Result(); res == 0 {
		return errors.New("您已经对该文章投过票了")
	}

	val := redisObj.HIncrBy(articleKey, "votes", 1).Val()
	redisObj.ZIncrBy("score:", voteNum, articleKey)

	dbObj.Model(models.Atricle{}).Where("id = ?", articleId).UpdateColumn("vote", val)
	return nil
}

func (model *ArticleLogic) List(orderType string, offset int64, limit int64) ([]map[string]string, error){
	redisObj, _ := config.GetRedis("default")

	var data []map[string]string
	var orderList []string
	switch orderType {
	case "vote":
		orderList = redisObj.ZRevRange("score:", offset, limit).Val()
	case "time":
		orderList = redisObj.ZRevRange("time:", offset, limit).Val()
	}

	for _, id := range orderList {
		data = append(data, redisObj.HGetAll(id).Val())
	}
	orderList = nil
	return data, nil
}

func (model *ArticleLogic) GetListByGroup(orderType string, offset int64, limit int64, group string) ([]map[string]string, error) {
	redisObj, _ := config.GetRedis("default")

	var data []map[string]string
	var orderList []string
	switch orderType {
	case "vote":
		err := redisObj.Do("zinterstore", "order:vote", 2, "score:", group).Err()
		if err != nil {
			return nil, err
		}
		orderList = redisObj.ZRevRange("order:vote", offset, limit).Val()
	case "time":
		err := redisObj.Do("zinterstore", "order:time", 2, "time:", group).Err()
		if err != nil {
			return nil, err
		}
		orderList = redisObj.ZRevRange("order:time", int64(offset), int64(limit)).Val()
	}

	for _, id := range orderList {
		data = append(data, redisObj.HGetAll(id).Val())
	}
	orderList = nil
	return data, nil
}

func addArticleToRedis(article *models.Atricle) error {
	redisObj, _ := config.GetRedis("default")

	redisMap := make(map[string]interface{})
	redisKey := "article:" + fmt.Sprintf("%d", article.ID)
	redisMap["title"] = article.Title
	redisMap["link"] = article.Link
	redisMap["poster"] = article.Author.Name + ":" + fmt.Sprintf("%d", article.Author.ID)
	redisMap["time"] = article.CreatedAt.Unix()
	redisMap["votes"] = article.Vote
	_, err := redisObj.HMSet(redisKey, redisMap).Result()
	if err != nil {
		return err
	}
	return nil
}

func addRedisZset(article *models.Atricle) error {
	redisObj, _ := config.GetRedis("default")
	member := "article:" + fmt.Sprintf("%d", article.ID)
	score := float64(article.CreatedAt.Unix())
	val := redis.Z{
		Score:  score,
		Member: member,
	}
	_, err := redisObj.ZAdd("time:", val).Result()
	if err != nil {
		return err
	}
	_, err = redisObj.ZAdd("score:", val).Result()
	if err != nil {
		return err
	}
	return nil
}

func addRedisSet(articleId int, userId int) error {
	redisObj, _ := config.GetRedis("default")

	voted := "voted:" + fmt.Sprintf("%d", articleId)
	redisObj.SAdd(voted, userId)
	redisObj.Expire(voted, time.Hour * 24 * 7)
	return nil
}